package com.gitee.wsl.ext.collection

import kotlin.math.max
import kotlin.math.min


/**
 * Default values taken from Guava.
 */
@PublishedApi
internal const val DEFAULT_COLLECTION_SIZE = 10

@PublishedApi
internal const val DEFAULT_COERCE_MINIMUM_VALUE = 16





/**
 * Calls the [selector] function for each element in the collection, providing the index of the element and [Boolean]s
 * indicating whether the element is the first or last element in the collection.
 */
inline fun <T> Iterable<T>.forEachIndexedExtended(
    selector: (index: Int, isFirst: Boolean, isLast: Boolean, value: T) -> Unit
) {
    var index = 0
    val iterator = iterator()
    var next: T
    while (iterator.hasNext()) {
        next = iterator.next()
        selector(index, index == 0, !iterator.hasNext(), next)
        index++
    }
}

/*inline fun <T> Iterable<T>.forEachIndexed(
    selector: (index: Int, isFirst: Boolean, isLast: Boolean, value: T) -> Unit
) {
    var index = 0
    val iterator = iterator()
    var next: T
    while (iterator.hasNext()) {
        next = iterator.next()
        selector(index, index == 0, !iterator.hasNext(), next)
        index++
    }
}*/



/**
 * Takes Iterable with pairs and returns a pair of collections filled with values in each part of pair.
 * */
fun <T, V> Iterable<Pair<T, V>>.splitPairCollection(): Pair<List<T>, List<V>> {
    val ts = mutableListOf<T>()
    val vs = mutableListOf<V>()
    for ((t, v) in this) {
        ts.add(t)
        vs.add(v)
    }
    return ts to vs
}

/**
 * Performs the given [action] on each element that is not null.
 */
inline fun <T : Any> Iterable<T?>.forEachNotNull(action: (T) -> Unit) {
    for (element in this) element?.let(action)
}

/**
 * Creates union of the given iterables.
 */
fun <T> Iterable<Iterable<T>?>.union(): Set<T> {
    val result = LinkedHashSet<T>()
    this.forEachNotNull { input -> result.addAll(input) }
    return result
}

/**
 * Checks that [this] map has [expectedSize] and if it is not the case (because value for some key was overwritten),
 * warning with affected keys is generated.
 */
@PublishedApi
internal inline fun <K, V, M : MutableMap<in K, in V>> M.checkUniqueness(expectedSize: Int, grouping: () -> Map<K, List<V>>) {
    if (this.size == expectedSize) {
        return
    }
    /*val duplicatedKeys = grouping().filterValues { it.size > 1 }

    @Suppress("ThrowingExceptionsWithoutMessageOrCause") // this is not true, we're not throwing it, we need only stacktrace
    val stack = Throwable().stackTraceToString()
    iterableLogger.warning {
        @Suppress("MagicNumber") // specified carefully, don't need constant
        val entries = duplicatedKeys.entries.toString().take(500) // ensures that huge collections will not consume too much space
        "The map should contain $expectedSize entries but the actual size is ${this.size}. The affected entries are $entries. $stack"
    }*/
}


/**
 * Returns default capacity for the maps based on the Guava's approach.
 */
@PublishedApi
internal fun <T> Iterable<T>.defaultMapCapacity() =
    mapCapacity(collectionSizeOrDefault(DEFAULT_COLLECTION_SIZE)).coerceAtLeast(
        DEFAULT_COERCE_MINIMUM_VALUE
    )

internal const val INT_MAX_POWER_OF_TWO: Int = Int.MAX_VALUE / 2 + 1
/**
 * Calculate the initial capacity of a map, based on Guava's com.google.common.collect.Maps approach. This is equivalent
 * to the Collection constructor for HashSet, (c.size()/.75f) + 1, but provides further optimisations for very small or
 * very large sizes, allows support non-collection classes, and provides consistency for all map based class construction.
 */
@PublishedApi
@Suppress("MagicNumber") // see upper docs
internal fun mapCapacity(expectedSize: Int): Int =
    when {
        expectedSize < 3 -> expectedSize + 1
        expectedSize < INT_MAX_POWER_OF_TWO -> expectedSize + expectedSize / 3
        else -> Int.MAX_VALUE // any large value
    }


/**
 * Returns the size of this iterable if it is known, or the specified [default] value otherwise.
 */
@PublishedApi
internal fun <T> Iterable<T>.collectionSizeOrDefault(default: Int): Int =
    if (this is Collection<*>) this.size else default


fun <T> Iterator<T>.collectUntil(predicate: (T) -> Boolean): List<T> {
    val result = mutableListOf<T>()
    while (hasNext()) {
        val v = next()
        if (predicate(v)) break
        result += v
    }

    return result
}



/**
 * Returns a range containing the smallest and largest among all values produced by the selector
 * function applied to each element in the iterable collection. Returns [IntRange.EMPTY] if the
 * iterator contains no elements.
 */
inline fun <T> Iterable<T>.rangeOf(selector: (T) -> Int): IntRange =
    if (!iterator().hasNext()) IntRange.EMPTY
    else
        map(selector)
            .map { IntRange(it, it) }
            .reduce { out, el -> IntRange(min(out.first, el.first), max(out.last, el.last)) }


/**
 * Creates a copy of [this] with [item] replaced with whatever is returned by [replacement].
 *
 * [item] **MUST** be an item contained in the original list, or you will get an [IndexOutOfBoundsException]
 * @see tryReplace
 * @return a list with all values of [this] with [item] replaced by [replacement]
 */
inline fun <T> Iterable<T>.replace(item: T, replacement: T.() -> T): List<T> {
    val newList = toMutableList()
    newList[indexOf(item)] = replacement(item)
    return newList
}

fun <T> Iterable<T>.startsWith(prefix: Iterable<T>): Boolean {
    val iter1 = iterator()
    val iter2 = prefix.iterator()
    while (iter2.hasNext()) {
        if (!iter1.hasNext()) return false
        if (iter1.next() != iter2.next()) return false
    }
    return true
}
